App.require(["canvasTool"], function (canvasTool) {
    var app = App.getCurrent();

    app.defineLayer("str-path-layer", {
        render: function (next) {
            this.initProperty();
            this.data.isChange = true;
            this.fetch("./index.html", function (text) {
                next(this.renderHTML(text));
            })
        },
        initProperty: function () {
            var property = this.property;

            property.id = property.id || this.uid;

            if (property.path) {
                var obj = canvasTool.changePath(property.path);
                this.data.action = obj.action;
                this.data.points = obj.points;
            }
            this.data.isMouseIn = false;
            this.data.attributeStr = this._createPropertyStr(property);
            if (property.animation) this.addPropertyAnimation(property.animation, this);
        },
        set hidden(value) {
            this.setValue("hidden", value);
        },
        get hidden() {
            return this.getValue("hidden");
        },
        get transform() {
            return this.getValue("transform");
        },
        set transform(value) {
            this.setValue("transform", value);
        },
        get fill() {
            return this.getValue("fill");
        },
        set fill(value) {
            this.setValue("fill", value);
        },
        get stroke() {
            return this.getValue("stroke");
        },
        set stroke(value) {
            this.setValue("stroke", value);
        },
        get linedash() {
            return this.getValue("linedash");
        },
        set linedash(value) {
            this.setValue("linedash", value);
        },
        get linedashoffset() {
            return this.getValue("linedashoffset");
        },
        set linedashoffset(value) {
            this.setValue("linedashoffset", value);
        },
        get linecap() {
            return this.getValue("linecap");
        },
        set linecap(value) {
            this.setValue("linecap", value);
        },
        get linewidth() {
            return this.getValue("linewidth");
        },
        set linewidth(value) {
            this.setValue("linewidth", value);
        },
        get miterlimit() {
            return this.getValue("miterlimit");
        },
        set miterlimit(value) {
            this.setValue("miterlimit", value);
        },
        get shadow() {
            return this.getValue("shadow");
        },
        set shadow(value) {
            this.setValue("shadow", value);
        },
        set path(value) {
            if (this.setValue("path", value)) {
                var obj = canvasTool.changePath(value);
                this.data.action = obj.action;
                this.data.points = obj.points;
            }
        },
        get path() {
            return this.getValue("path");
        },
        set opacity(value) {
            this.setValue("opacity", value);
        },
        get opacity() {
            return this.getValue("opacity");
        },
        get animation() {
            return this.getValue("animation");
        },
        set animation(value) {
            if (this.setValue("animation", value)) {
                if (value) this.addPropertyAnimation(value, this);
            }
        },
        set transition(value) {
            this.setValue("transition", value);
        },
        get transition() {
            return this.getValue("transition");
        },
        set marker(value) {
            this.setValue("marker", value);
        },
        get marker() {
            return this.getValue("marker");
        },
        set markerstart(value) {
            this.setValue("markerstart", value);
        },
        get markerstart() {
            return this.getValue("markerstart");
        },
        set markerend(value) {
            this.setValue("markerend", value);
        },
        get markerend() {
            return this.getValue("markerend");
        },
        moveLayerHandler: function (ev, ctx, ratio) {
            if (this.containsBounds(ctx, ev.clientX, ev.clientY, ratio)) {
                if (!this.data.isMouseIn) {
                    this.data.isMouseIn = true;
                    this.dispatchCustomEvent("pathlayermouseenter", {
                        component: this,
                        event: ev
                    });
                }
            }
            else {
                if (this.data.isMouseIn) {
                    this.data.isMouseIn = false;
                    this.dispatchCustomEvent("pathlayermouseleave", {
                        component: this,
                        event: ev
                    });
                }
            }
        },
        clickLayerHandler: function (ev, ctx, ratio) {
            if (this.containsBounds(ctx, ev.clientX, ev.clientY, ratio)) {
                this.dispatchCustomEvent("pathlayerclick", {
                    component: this,
                    event: ev
                });
            }
        },
        drawMarker: function (ctx, ratio, bk) {
            var dObj = this.getPropertyUse(["path"]);
            ratio = ratio || 1;
            ctx.beginPath();
            var tempAction = dObj.path.action, tempPoint = dObj.path.points;
            var startPoint = {}, prevPoint = {};
            var index = 0;
            for (var i = 0, len = tempAction.length; i < len; i++) {
                if (tempAction[i] === "M") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++];
                    if (bk) bk(ctx, p0 * ratio, p1 * ratio, 0)
                    startPoint.x = p0;
                    startPoint.y = p1;
                    prevPoint.x = p0;
                    prevPoint.y = p1;
                } else if (tempAction[i] === "L" || tempAction[i] === "H" || tempAction[i] === "V") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++];
                    if (bk) bk(ctx, p0 * ratio,  p1* ratio, Math.atan2(p1 - prevPoint.y, p0 - prevPoint.x));
                    prevPoint.x = p0;
                    prevPoint.y = p1;
                } else if (tempAction[i] === "C" || tempAction[i] === "S") {
                    index += 2;
                    cc
                    if (bk) bk(ctx, p4 * ratio, p5 * ratio, Math.atan2(p5 - p3, p4 - p2));
                    prevPoint.x = p4;
                    prevPoint.y = p5;
                } else if (tempAction[i] === "Z") {
                    // if (bk && this.markerend) bk(ctx, startPoint.x * ratio, startPoint.y * ratio, Math.atan2(startPoint.y - prevPoint.y, startPoint.x - prevPoint.x));
                } else if (tempAction[i] === "Q" || tempAction[i] === "T") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], p3 = tempPoint[index++];
                    if (bk) bk(ctx, p2, p3, Math.atan2(p3 - p1, p2 - p0));
                    prevPoint.x = p2;
                    prevPoint.y = p3;
                } else if (tempAction[i] === "A") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], 
                        p3 = tempPoint[index++], p4 = tempPoint[index++], p5 = tempPoint[index++],
                        p6 = tempPoint[index++], p7 = tempPoint[index++], p8 = tempPoint[index++];
                    var theta = p4,
                        fa = p5 * ratio,
                        fs = p6 * ratio,
                        rx = p2 * ratio,
                        ry = p3 * ratio,
                        x2 = p7 * ratio,
                        y2 = p8 * ratio,
                        x1 = p0 * ratio,
                        y1 = p1 * ratio;

                    var cos = Math.cos(theta),
                        sin = Math.sin(theta);
                    var nx1 = cos * (x1 - x2) / 2 + sin * (y1 - y2) / 2,
                        ny1 = -sin * (x1 - x2) / 2 + cos * (y1 - y2) / 2;
                    var tempNumber = (rx * rx * ry * ry - rx * rx * ny1 * ny1 - ry * ry * nx1 * nx1);
                    tempNumber = tempNumber < 0 ? 0 : tempNumber;
                    var tempN = Math.sqrt(
                        tempNumber /
                        (rx * rx * ny1 * ny1 + ry * ry * nx1 * nx1)
                    );

                    if (fa !== fs) {
                        tempN = -tempN;
                    }
                    var ncx = tempN * rx * ny1 / ry,
                        ncy = -tempN * ry * nx1 / rx;

                    var t1 = this.arc(1, 0, (nx1 - ncx) / rx, (ny1 - ncy) / ry);
                    var dt = this.arc((nx1 - ncx) / rx, (ny1 - ncy) / ry, (-nx1 - ncx) / rx, (-ny1 - ncy) / ry);
                    var t2 = dt + t1;
                    if (bk) bk(ctx, p7 * ratio, p8 * ratio, this.getArcTheta(rx, ry, theta, t2, fs));
                    prevPoint.x = p7;
                    prevPoint.y = p8;
                }
            }
        },
        getArcTheta: function (rx, ry, theta, t2, fs) {
            var dTheta = Math.atan2(-ry * Math.cos(t2), rx * Math.sin(t2));
            if (fs) return theta - dTheta;
            return theta + dTheta;
        },
        getLength: function () {
            var dObj = this.getPropertyUse(["path"]);
            var tempAction = dObj.path.action, tempPoint = dObj.path.points;
            var startPoint = {}, length = 0, prevPoint = {};
            var index = 0;
            for (var i = 0, len = tempAction.length; i < len; i++) {
                if (tempAction[i] === "M") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++];
                    startPoint.x = p0;
                    startPoint.y = p1;
                    prevPoint.x = p0;
                    prevPoint.y = p1;
                } else if (tempAction[i] === "L" || tempAction[i] === "H" || tempAction[i] === "V") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++];
                    length += this.getLengthByLine(prevPoint, {x: p0, y: p1});
                    prevPoint.x = p0;
                    prevPoint.y = p1;
                } else if (tempAction[i] === "C" || tempAction[i] === "S") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], p3 = tempPoint[index++], p4 = tempPoint[index++], p5 = tempPoint[index++];
                    length += this.getLengthByCurve(prevPoint, {x: p0, y: p1}, {x: p2, y: p3}, {x: p4, y: p5});
                    prevPoint.x = p4;
                    prevPoint.y = p5;
                } else if (tempAction[i] === "Z") {
                    length += this.getLengthByLine(prevPoint, startPoint);
                } else if (tempAction[i] === "Q" || tempAction[i] === "T") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], p3 = tempPoint[index++];
                    length += this.getLengthByQuadratic(prevPoint, {x:p0, y:p1}, {x:p2, y: p3});
                    prevPoint.x = p2;
                    prevPoint.y = p3;
                } else if (tempAction[i] === "A") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], 
                        p3 = tempPoint[index++], p4 = tempPoint[index++], p5 = tempPoint[index++],
                        p6 = tempPoint[index++], p7 = tempPoint[index++], p8 = tempPoint[index++];
                    var theta = p4,
                        fa = p5 * ratio,
                        fs = p6 * ratio,
                        rx = p2 * ratio,
                        ry = p3 * ratio,
                        x2 = p7 * ratio,
                        y2 = p8 * ratio,
                        x1 = p0 * ratio,
                        y1 = p1 * ratio;

                    var cos = Math.cos(theta),
                        sin = Math.sin(theta);
                    var nx1 = cos * (x1 - x2) / 2 + sin * (y1 - y2) / 2,
                        ny1 = -sin * (x1 - x2) / 2 + cos * (y1 - y2) / 2;
                    var tempNumber = (rx * rx * ry * ry - rx * rx * ny1 * ny1 - ry * ry * nx1 * nx1);
                    tempNumber = tempNumber < 0 ? 0 : tempNumber;
                    var tempN = Math.sqrt(
                        tempNumber /
                        (rx * rx * ny1 * ny1 + ry * ry * nx1 * nx1)
                    );

                    if (fa !== fs) {
                        tempN = -tempN;
                    }
                    var ncx = tempN * rx * ny1 / ry,
                        ncy = -tempN * ry * nx1 / rx;

                    var t1 = this.arc(1, 0, (nx1 - ncx) / rx, (ny1 - ncy) / ry);
                    var dt = this.arc((nx1 - ncx) / rx, (ny1 - ncy) / ry, (-nx1 - ncx) / rx, (-ny1 - ncy) / ry);
                    var t2 = dt + t1;
                    prevPoint.x = p7;
                    prevPoint.y = p8;
                    length += this.getLengthByArc(rx, ry, t1, t2, fs);
                }
            }

            return length;
        },
        createPath: function (ctx, ratio) {
            var dObj = this.getPropertyUse(["path"]);
            var tempAction = dObj.path.action, tempPoint = dObj.path.points;
            ratio = ratio || 1;
            ctx.beginPath();
            var index = 0;
            for (var i = 0, len = tempAction.length; i < len; i++) {
                if (tempAction[i] === "M") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++];
                    ctx.moveTo(p0 * ratio, p1 * ratio, 0);
                } else if (tempAction[i] === "L" || tempAction[i] === "H" || tempAction[i] === "V") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++];
                    ctx.lineTo(p0 * ratio, p1 * ratio);
                } else if (tempAction[i] === "C" || tempAction[i] === "S") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], p3 = tempPoint[index++], p4 = tempPoint[index++], p5 = tempPoint[index++];
                    ctx.bezierCurveTo(p0 * ratio, p1 * ratio, p2 * ratio, p3 * ratio, p4 * ratio, p5 * ratio);
                } else if (tempAction[i] === "Z") {
                    ctx.closePath();
                } else if (tempAction[i] === "Q" || tempAction[i] === "T") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], p3 = tempPoint[index++];
                    ctx.quadraticCurveTo(p0 * ratio, p1 * ratio, p2 * ratio, p3 * ratio);
                } else if (tempAction[i] === "A") {
                    var p0 = tempPoint[index++], p1 = tempPoint[index++], p2 = tempPoint[index++], 
                        p3 = tempPoint[index++], p4 = tempPoint[index++], p5 = tempPoint[index++],
                        p6 = tempPoint[index++], p7 = tempPoint[index++], p8 = tempPoint[index++];
                    var theta = p4,
                        fa = p5 * ratio,
                        fs = p6 * ratio,
                        rx = p2 * ratio,
                        ry = p3 * ratio,
                        x2 = p7 * ratio,
                        y2 = p8 * ratio,
                        x1 = p0 * ratio,
                        y1 = p1 * ratio;

                    var cos = Math.cos(theta),
                        sin = Math.sin(theta);
                    var nx1 = cos * (x1 - x2) / 2 + sin * (y1 - y2) / 2,
                        ny1 = -sin * (x1 - x2) / 2 + cos * (y1 - y2) / 2;
                    var tempNumber = (rx * rx * ry * ry - rx * rx * ny1 * ny1 - ry * ry * nx1 * nx1);
                    tempNumber = tempNumber < 0 ? 0 : tempNumber;
                    var tempN = Math.sqrt(
                        tempNumber /
                        (rx * rx * ny1 * ny1 + ry * ry * nx1 * nx1)
                    );

                    if (fa !== fs) {
                        tempN = -tempN;
                    }
                    var ncx = tempN * rx * ny1 / ry,
                        ncy = -tempN * ry * nx1 / rx;

                    var cx = cos * ncx - sin * ncy + (x1 + x2) / 2,
                        cy = sin * ncx + cos * ncy + (y1 + y2) / 2;

                    var t1 = this.arc(1, 0, (nx1 - ncx) / rx, (ny1 - ncy) / ry);
                    var dt = this.arc((nx1 - ncx) / rx, (ny1 - ncy) / ry, (-nx1 - ncx) / rx, (-ny1 - ncy) / ry);
                    var t2 = dt + t1;
                    if (fs) {
                        ctx.ellipse(cx, cy, rx, ry, theta, t1, t2, true);
                    } else {
                        ctx.ellipse(cx, cy, rx, ry, theta, t1, t2, false);
                    }
                }
            }
        },
        getLengthByArc: function (rx, ry, t1, t2, isClockWise) {
            if (Math.abs(t2-t1)< 0.1) {
                var theta = (t2 + t1) / 2, p = { x: rx * Math.cos(theta), y: ry * Math.sin(theta) };
                    r = this.getLengthByLine({x: 0, y: 0}, p);
                return r * Math.abs(t2 - t1);
            }
            else {
                if (isClockWise) {
                    while (t2 > t1) t2 -= 2 * Math.PI;
                    while (t1 - t2 > 2 * Math.PI) t1 -= 2 * Math.PI;
                }
                else {
                    while (t2 < t1) t2 += 2 * Math.PI;
                    while (t2 - t1 > 2 * Math.PI) t2 -= 2 * Math.PI;
                }
                var halfTheta = (t2 + t1) / 2;
                return this.getLengthByArc(rx, ry, t1, halfTheta, isClockWise) + this.getLengthByArc(rx, ry, halfTheta, t2, isClockWise);
            }
        },
        getLengthByLine: function (p1, p2) {
            var deltaX = p1.x - p2.x, deltaY = p1.y - p2.y;
            return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        },
        getLengthByCurve: function (p1, p2, p3, p4) {
            if (this.normalDot(this.getNormal(p4, p1), this.getNormal(p3, p2)) > 0.99 || this.getLengthByLine(p4, p1) < 4) return this.getLengthByLine(p4, p1);
            var p23 = { x: (p2.x + p3.x) / 2, y: (p2.y + p3.y) / 2 },
                p12 = { x: (p2.x + p1.x) / 2, y: (p2.y + p1.y) / 2 },
                p34 = { x: (p3.x + p4.x) / 2, y: (p3.y + p4.y) / 2 },
                p123 = { x: (p12.x + p23.x) / 2, y: (p12.y + p23.y) / 2 },
                p234 = { x: (p23.x + p34.x) / 2, y: (p23.y + p34.y) / 2 },
                p1234 = { x: (p123.x + p234.x) / 2, y: (p123.y + p234.y) / 2 };
            return getLengthByCurve(p1, p12, p123, p1234) + this.getLengthByCurve(p1234, p234, p34, p4);
        },
        getLengthByQuadratic: function (p1, p2, p3) {
            if (this.normalDot(this.getNormal(p2, p1), this.getNormal(p3, p2)) > 0.99 || this.getLengthByLine(p3, p1) < 4) return this.getLengthByLine(p3, p1);
            var halfPoint = {
                x: (2 * p2.x + p1.x + p3.x) / 4,
                y: (p1.y + 2 * p2.y + p3.y) / 4
            };
            return this.getLengthByQuadratic(p1, { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 }, halfPoint) 
                 + this.getLengthByQuadratic(halfPoint, { x: (p2.x + p3.x) / 2, y: (p2.y + p3.y) / 2 }, p3);
        },
        normalDot: function (n1, n2) {
            return n1.x * n2.x + n1.y * n2.y;
        },
        getNormal: function (p2, p1) {
            var len = this.getLengthByLine(p2, p1);
            return {
                x: (p2.x - p1.x) / len,
                y: (p2.y - p1.y) / len
            };
        },
        arc: function (x1, y1, x2, y2) {
            var sign = x1 * y2 - x2 * y1;
            var temp = (x1 * x2 + y1 * y2) / Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2);
            if (temp < -1) temp = -1;
            if (temp > 1) temp = 1;
            var n = Math.acos(temp);
            if (sign > 0) return n;
            else return -n;
        },
        drawSelf: function (ctx, ratio) {
            var dObj = this.getPropertyUse(["opacity", "shadow", "linedash", "fill", "stroke", "linedashoffset", "linewidth", "miterlimit", "linejoin", "linecap", "path"]);
            var property = this.property;
            ctx.save();
            if (typeof dObj.opacity !== "undefined") ctx.globalAlpha = parseFloat(dObj.opacity);
            if (dObj.path) {
                var keyToCanvas = canvasTool.keyToCanvas;
                for (var key in property) {
                    var ctxKey = keyToCanvas[key];
                    if (ctxKey) {
                        if (ctxKey == "fillStyle" || ctxKey == "strokeStyle") ctx[ctxKey] = this.getFillOrStrokeStyle(dObj[key]);
                        else ctx[ctxKey] = dObj[key];
                    }
                }
    
                if (dObj.linedash) this.useLineDash(ctx, dObj.linedash, dObj.linedashoffset, ratio);
                if (dObj.shadow) this.useShadow(ctx, dObj.shadow, ratio);
                this.transformView(ctx, ratio);
                this.createPath(ctx, ratio);
                if (dObj.fill) ctx.fill();
                if (dObj.stroke) ctx.stroke();
                ctx.restore();

                if (this.marker) {
                    var marker = this.getMarkerByStr(this.marker, this, ratio);
                    if (!marker) return;
                    this.drawMarker(ctx, ratio, function (ctx, x, y, theta) {
                        ctx.save();
                        ctx.translate(x, y);
                        if (marker.rotate === "auto") ctx.rotate(theta);
                        marker.drawToCanvas(ctx, ratio);
                        ctx.restore();
                    })
                }
            }
        },
        dispose: function () {
            this.removeAnimation();
            this.data.isChange = true;
            this.data.isMouseIn = false;
        }
    })
})